#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <syslog.h>
#include "pppoe-server.h"
#include "logging.h"
#include "sess-func.h"

struct PppoeSessionFunctionTable default_sft = {
    pppoe_stop_session,
    pppoe_session_is_active,
    NULL,
};

void pppoe_stop_session(struct Session *ss, char const *reason)
{
    struct PppoeConection conn;

    memset(&conn, 0, sizeof(struct PppoeConection));
    conn.use_host_uniq = 0;

    memcpy(conn.local_mac, ss->eth_if->mac, ETH_ALEN);
    conn.disc_sock = ss->eth_if->sock;
    conn.sid = ss->sid;
    memcpy(conn.peer_mac, ss->mac, ETH_ALEN);
    sendPADT(&conn, reason);
    ss->flags |= FLAG_SENT_PADT;

    if (ss->pid != 0) {
        kill(ss->pid, SIGTERM);
    }
    ss->func = &default_sft;
}

int pppoe_session_is_active(struct Session *ss)
{
    return (ss->pid != 0);
}

void stop_all_session(void)
{
    struct Session *ss = busy_sess;
    while (ss) {
        ss->func->stop(ss, "shutting down");
        ss = ss->next;
    }
}

// TO-DO
int send_packet(struct PppoeConection *conn, int sock, struct PppoePacket *pkt, int size)
{
    if (send(sock, pkt, size, 0) < 0 && (errno != ENOBUFS)) {
        pppoe_error("calling send within send_packet");
        return -1;
    }
    return 0;
}

void sendPADT(struct PppoeConection *conn, char const *msg)
{
    struct PppoePacket packet;
    unsigned char *cursor = packet.payload;
    unsigned int plen = 0;
    struct PppoeTag host_uniq;
    struct PppoeTag err;
    pid_t pid;
    size_t elen;

    if (conn->sid == 0) {
        return;
    }
    if (conn->disc_sock < 0) {
        return;
    }
    memcpy(packet.eth_hdr.ether_dhost, conn->peer_mac, ETH_ALEN);
    memcpy(packet.eth_hdr.ether_shost, conn->local_mac, ETH_ALEN);
    packet.eth_hdr.ether_type = htons(ETH_P_PPP_DISC);
    packet.ver = 1;
    packet.type = 1;
    packet.code = CODE_PADT;

    packet.sid = conn->sid;
    conn->sid = 0;
    if (conn->use_host_uniq) {
        pid = getpid();
        host_uniq.type = htons(TAG_HOST_UNIQ);
        host_uniq.length = htons(sizeof(pid_t));
        memcpy(host_uniq.payload, &pid, sizeof(pid_t));
        memcpy(cursor, &host_uniq, sizeof(pid_t) + TAG_HDR_SIZE);
        cursor += sizeof(pid_t) + TAG_HDR_SIZE;
        plen += sizeof(pid_t) + TAG_HDR_SIZE;
    }
    if (msg) {
        elen = strlen(msg);
        err.type = htons(TAG_GENERIC_ERROR);
        err.length = htons(elen);
        strcpy((char *)err.payload, msg);
        memcpy(cursor, &err, elen + TAG_HDR_SIZE);
        cursor += elen + TAG_HDR_SIZE;
        plen += elen + TAG_HDR_SIZE;
    }
    if (conn->cookie.type) {
        CHECK_ROOM(cursor, packet.payload, ntohs(conn->cookie.length) + TAG_HDR_SIZE);
        memcpy(cursor, &conn->cookie, ntohs(conn->cookie.length) + TAG_HDR_SIZE);
        cursor += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
        plen += ntohs(conn->cookie.length) + TAG_HDR_SIZE;
    }
    if (conn->relay_id.type) {
        CHECK_ROOM(cursor, packet.payload, ntohs(conn->relay_id.length) + TAG_HDR_SIZE);
        memcpy(cursor, &conn->relay_id, ntohs(conn->relay_id.length) + TAG_HDR_SIZE);
        cursor += ntohs(conn->relay_id.length) + TAG_HDR_SIZE;
        plen += ntohs(conn->relay_id.length) + TAG_HDR_SIZE;
    }
    packet.length = htons(plen);
    if (send_packet(conn, conn->disc_sock, &packet, (int)(plen + FIXED_OVERHEAD)) != -1) {
        syslog(LOG_INFO, "sent PADI");
    }
}

